/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun * Microsystems, Inc. All Rights Reserved. */ package org.netbeans.modules.jini; import java.beans.*; import java.util.*; import java.io.*; import java.rmi.*; import java.rmi.server.*; import java.net.*; import net.jini.core.discovery.*; import net.jini.core.lookup.*; import net.jini.discovery.*; import net.jini.lookup.*; import org.openide.util.*; /** * An cached implementation of Jini services browser. * Contains simple constructors for three basic discovery scenarios: * <li> all services in <groups> in multicast domain * <li> all services at <located registrars> * <li> <templetd services> at all groups at multicast domain * * <p>User can register some listeners: * <li> addListener(ServiceDiscoveryListener l) new service * <li> addDiscoveryListener(DiscoveryListener l) new LUS * <li> addPropertyChangeListener(PropertyChangeListener l); * <P>Fires: * <li> "services" a new service discovered, ... * <li> "entries" ... * <li> "events" a new djinn event occured * * @author Petr Kuzel * @version */ public class BrowserModel extends Object implements DiscoveryListener, DiscoveryManagement, ServiceDiscoveryListener { // -- caching -- // cache of discovered services private LookupCache cache; // defenes WHERE to search private LookupDiscoveryManager dmgr; // chache provider private ClientLookupManager lmgr; private final static ServiceItemFilter ALL = new NoFilter(); // default instance caching LUSes private static BrowserModel model; /** Discovered LUS SID -> String[] of groups * @associates String*/ private HashMap groups = new HashMap(); /** Discovered LUS SID -> LookupLocators */ private HashMap locators = new HashMap(); /** DiscoveryListener -> DiscoveryListenerWrapper */ private HashMap wrappers = new HashMap(); /** Entry class -> list of SIDs */ private HashMap entries = new HashMap(); /** Service class -> list of SIDs */ private HashMap services = new HashMap(); /** Holds value of property events. * @associates BrowserEvent*/ private LinkedList events = new LinkedList(); /** Utility field used by bound properties. */ private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); /** All registered discovery listeners. */ private HashSet discoveryListeners = new HashSet(); /** * Creates model caching all services registered at LUSes reachable throught * multicasting and being members of any group passed. */ public BrowserModel(String[] groups) { this(groups, null, null); } public BrowserModel(String grp) { this(new String[] {grp}); } public BrowserModel(Groups groups) { this(groups.getGroups()); } /** * Creates model caching all services registered at LUSes reachable throught * passed locators. */ public BrowserModel(LookupLocator[] locators) { this(LookupDiscovery.NO_GROUPS, locators, null); } public BrowserModel(LookupLocator loc) { this(new LookupLocator[] {loc}); } /** * Creates model caching templated services registered at LUSes reachable throught * multicasting (i.e. ALL_GROUPS). */ public BrowserModel(ServiceTemplate template) { this(LookupDiscovery.ALL_GROUPS, null, template); } /** Creates new LookupModel that caches Jini services. * @param groups to discover (multicast) * @param locators lookup these LUSes (unicast) * @param template services that should be included */ public BrowserModel(String[] groups, LookupLocator[] locators, ServiceTemplate template) { annotateClasses(); createCache(groups, locators, template); } /** Discover all services in all groups */ public BrowserModel() { this(new ServiceTemplate(null, null, null)); } /** Shared BrowserModel instance caching local LUSes * can be obtained by call to this method. */ public static synchronized BrowserModel getDefault() { ServiceTemplate template = new ServiceTemplate(null, new Class[] {ServiceRegistrar.class}, null); if (model == null) model = new BrowserModel(template); return model; } /** Is anybody interested in asynchronous browsing? */ public void addListener(ServiceDiscoveryListener l) { cache.addListener(l); } /** Indicate no more interest. */ public void removeListener(ServiceDiscoveryListener l) { cache.removeListener(l); } public void addDiscoveryListener(DiscoveryListener l) { // wrap it to be always the first who knows discoveryListeners.add(l); } public void removeDiscoveryListener(DiscoveryListener l) { discoveryListeners.remove(l); } /** Add a PropertyChangeListener to the listener list. * @param l The listener to add. */ public void addPropertyChangeListener(java.beans.PropertyChangeListener l) { propertyChangeSupport.addPropertyChangeListener (l); } /** Removes a PropertyChangeListener from the listener list. * @param l The listener to remove. */ public void removePropertyChangeListener(java.beans.PropertyChangeListener l) { propertyChangeSupport.removePropertyChangeListener (l); } public ServiceRegistrar[] getRegistrars() { return dmgr.getRegistrars(); } public void discard(ServiceRegistrar proxy) { dmgr.discard(proxy); } public void addLocators(LookupLocator[] locs) { dmgr.addLocators(locs); } public void removeLocators(LookupLocator[] locs) { dmgr.removeLocators(locs); } public void setLocators(LookupLocator[] locs) { dmgr.setLocators(locs); } public void addGroups(String[] grps) throws IOException { dmgr.addGroups(grps); } public void removeGroups(String[] grps) throws IOException { dmgr.removeGroups(grps); } public void setGroups(String[] grps) throws IOException { dmgr.setGroups(grps); } /** End all underlayng threads. */ public void terminate() { dmgr.terminate(); lmgr.terminate(); cache.terminate(); } /** * Annotate classes by URL that can serve internal http server. * Using introspection cange codebase field. * <p> Warning: depends on SUn's implementation of RMI. */ private void annotateClasses() { try { java.lang.reflect.Field f = Class.forName("sun.rmi.server.LoaderHandler").getDeclaredField("codebase"); f.setAccessible(true); String codebase = HttpServer.getResourceRoot().toExternalForm(); f.set(null, codebase); } catch (Exception ex) { System.err.println("can not set codebase " + ex); } } /** Create a new cache of discovered services */ private void createCache(String[] groups, LookupLocator[] locators, ServiceTemplate template) { template = template == null ? new ServiceTemplate(null, null, null) : template; try { dmgr = new LookupDiscoveryManager(groups, locators, this); lmgr = new ClientLookupManager(dmgr, null); cache = lmgr.createLookupCache(template, ALL, this); } catch (RemoteException ex) { // cache cannot create remote listener for LUS System.err.println(ex); } catch (IOException ex) { // cannot do multicast discovery - can not allocate socket. System.err.println(ex); } // System.err.println("Cache created."); } /** Obtain names of up to current time available LUS groups. * @return array (may be empty) of strings (group names). */ public String[] getDiscoveredGroups() { TreeSet set = new TreeSet(java.text.Collator.getInstance()); Iterator it = groups.values().iterator(); while (it.hasNext()) { String[] grps = (String[]) it.next(); set.addAll(Arrays.asList(grps)); } if (set.remove("")) set.add("public"); String[] ret = new String[set.size()]; new Vector(set).toArray(ret); return ret; } /** Obtain names of up to current time available LUS locators. * @return array of locators (may be empty). */ public LookupLocator[] getDiscoveredLocators() { HashSet set = new HashSet(locators.values()); LookupLocator[] ret = new LookupLocator[set.size()]; new Vector(set).toArray(ret); return ret; } /** Getter for property events. * @return unmodifiable list of recent events. */ public List getEvents() { return Collections.unmodifiableList(events); } /** * Add one event, as side effect canremove last one. */ protected void logEvent(BrowserEvent e) { while (events.size() > org.netbeans.modules.jini.settings.JiniSettings.DEFAULT.getEventLimit()) { events.removeLast(); } events.addFirst(e); setEvents(events); } /** Setter for property events. * @param events New value of property events. */ protected void setEvents(LinkedList events) { LinkedList oldEvents = this.events; this.events = events; propertyChangeSupport.firePropertyChange ("events", null, events); } // // Discovery stage routines // /** Called when one or more lookup service registrars has been discovered. * The method should return quickly; e.g., it should not make remote * calls. * * @param e the event that describes the discovered registrars */ public void discovered(DiscoveryEvent e) { // update groups and allow access from that address ServiceRegistrar[] regs = e.getRegistrars(); for (int i=0; i<regs.length; i++) { ServiceRegistrar reg = regs[i]; try { String _groups[] = reg.getGroups(); ServiceID sid = reg.getServiceID(); LookupLocator loc = reg.getLocator(); logEvent(new BrowserEvent.DiscoveredLUS(loc)); InetAddress adr = InetAddress.getByName(loc.getHost()); groups.put(sid, _groups); locators.put(sid, loc); HttpServer.allowAccess(adr); } catch (java.net.UnknownHostException ex) { System.err.println(ex); } catch (RemoteException ex) { //TODO discard it System.err.println(ex); } } // forvard event Enumeration en = Collections.enumeration(discoveryListeners); while (en.hasMoreElements()) { DiscoveryListener lis = (DiscoveryListener) en.nextElement(); lis.discovered(e); } } /** Called when one or more lookup service registrars has been discarded. * The method should return quickly; e.g., it should not make remote * calls. * * @param e the event that describes the discarded registrars */ public void discarded(DiscoveryEvent e) { // update groups ServiceRegistrar[] regs = e.getRegistrars(); for (int i=0; i<regs.length; i++) { ServiceRegistrar reg = regs[i]; ServiceID sid = reg.getServiceID(); logEvent(new BrowserEvent.DiscardedLUS((LookupLocator)locators.get(sid))); groups.remove(sid); locators.remove(sid); } // forward event Enumeration en = Collections.enumeration(discoveryListeners); while (en.hasMoreElements()) { DiscoveryListener lis = (DiscoveryListener) en.nextElement(); lis.discarded(e); } } // // Lookup stage routines. // // Monitor all discovered services' entries and interfaces // // Implementation of BrowserListener background routines // public void serviceAdded(ServiceDiscoveryEvent event) { ServiceItem item = event.getPostEventServiceItem(); logEvent(new BrowserEvent.DiscoveredService(item)); addServiceClass(item.service.getClass(), item.serviceID); for (int i = 0; i<item.attributeSets.length; i++) { addEntryClass(item.attributeSets[i].getClass(), item.serviceID); } } public void serviceRemoved(ServiceDiscoveryEvent event) { ServiceItem item = event.getPreEventServiceItem(); logEvent(new BrowserEvent.DiscardedService(item)); removeServiceClass(item.service.getClass(), item.serviceID); for (int i = 0; i<item.attributeSets.length; i++) { removeEntryClass(item.attributeSets[i].getClass(), item.serviceID); } } public void serviceChanged(ServiceDiscoveryEvent event) { serviceRemoved(event); serviceAdded(event); } /** */ private void addEntryClass(Class clzz, ServiceID id) { if (addClass(entries, clzz, id)) fireUpdateBrowser("entries"); } private void removeEntryClass(Class clzz, ServiceID id) { if (removeClass(entries, clzz, id)) fireUpdateBrowser("entries"); } private void addServiceClass(Class clzz, ServiceID id) { if (addClass(services, clzz, id)) fireUpdateBrowser("services"); } private void removeServiceClass(Class clzz, ServiceID id) { if (removeClass(services, clzz, id)) fireUpdateBrowser("services"); } /** @return true if first added. */ private boolean addClass(Map map, Class clzz, ServiceID id) { synchronized (map) { // System.err.println("adding " + clzz + " " + id + " at " + map); boolean first = false; Set ids = (Set) map.get(clzz); if (ids == null) { first = true; ids = new HashSet(); ids.add(id); } else { ids.add(id); } map.put(clzz, ids); return first; } } /** @return true if last removed. */ private boolean removeClass(Map map, Class clzz, ServiceID id) { synchronized (map) { boolean last = false; Set ids = (Set) map.get(clzz); if (ids != null) { ids.remove(id); if (ids.size() == 0) { map.remove(clzz); last = true; } } return last; } } /** @return collated set of classes representing all discovered interfaces. */ public Set getServiceInterfaces() { synchronized (services) { TreeSet ret = new TreeSet(new Util.ClassCollator()); Enumeration en = Collections.enumeration(services.keySet()); while (en.hasMoreElements()) { Class clzz = (Class) en.nextElement(); Class[] ifs = clzz.getInterfaces(); ret.addAll(Arrays.asList(ifs)); } return ret; } } /** @return collated set classes representing all entries of discovered services. */ public Set getEntryClasses() { synchronized (entries) { TreeSet ret = new TreeSet(new Util.ClassCollator()); ret.addAll(entries.keySet()); return ret; } } /** @return collated set of classes representying all discovered services. */ public Set getServiceClassses() { synchronized (services) { TreeSet ret = new TreeSet(new Util.ClassCollator()); ret.addAll(services.keySet()); return ret; } } private synchronized void fireUpdateBrowser(String prop) { if ("entries".equals(prop)) propertyChangeSupport.firePropertyChange("entries", null, null); else if ("services".equals(prop)) propertyChangeSupport.firePropertyChange("services", null, null); } /** Filter without any filter ability. */ private static class NoFilter implements ServiceItemFilter { public boolean check(ServiceItem item) { return true; } } // // Basic self test. // /** Test this class * @param args the command line arguments */ public static void main (String[] args) throws Exception { //!!! System.setSecurityManager(new RMISecurityManager()); BrowserModel me = BrowserModel.getDefault(); Test test = new Test(); me.addListener(test); Thread.currentThread().join(10000); System.err.println("Groups: "); String groups[] = me.getDiscoveredGroups(); for (int i=0; i<groups.length; i++) { System.err.print(groups[i] + ", "); } System.err.println(""); me.terminate(); System.err.println("Self-test done."); } /** For self-testing purposes. */ private static class Test implements ServiceDiscoveryListener { public void serviceAdded(ServiceDiscoveryEvent event) { System.err.println("Added: " + event.getPostEventServiceItem()); } public void serviceRemoved(ServiceDiscoveryEvent event) { System.err.println("Removed: " + event.getPreEventServiceItem()); } public void serviceChanged(ServiceDiscoveryEvent event) { System.err.println("Changed: " + event.getPostEventServiceItem()); } } } /* * <<Log>> * 2 Gandalf 1.1 2/3/00 Petr Kuzel Be smart and documented * 1 Gandalf 1.0 2/2/00 Petr Kuzel * $ */